package uk.ac.aber.paws.server.autosleep;

import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;

import javax.mail.MessagingException;
import javax.naming.directory.SearchControls;

import uk.ac.aber.paws.server.db.DBLogger;
import uk.ac.aber.paws.server.config.ConfigManager;
import uk.ac.aber.paws.server.core.PAWSServer;
import uk.ac.aber.paws.server.ldap.LDAPQuery;
import uk.ac.aber.paws.server.net.SendMessage;
import uk.ac.aber.paws.server.net.WakeOnLan;
import uk.ac.aber.paws.server.util.TextManip;
import uk.ac.aber.paws.server.util.TimeChecks;
import static uk.ac.aber.paws.server.config.Constants.*;

/**
 * @author Rob Johnson, Aberystwyth University
 * @email rob.johnson@aber.ac.uk
 * @date 16th August 2011
 * 
 * ProfileProcessor.java
 * 
 * Contains all generic constants used within the application
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

public class ProfileProcessor extends Thread {
		
	Hashtable<String,Object> profileInfo;
	
	public ProfileProcessor(Hashtable<String,Object> profileInfo) {
		this.profileInfo = profileInfo;
		
		this.start();
	}
	
	public void run() {
		
		try {
			startProcess();
		}
		catch (MessagingException ex) {
			ex.printStackTrace();
		}
	}
	
	private void startProcess() throws MessagingException {
		
		// create a SleepProfile object and store the profile info from the database in here
		SleepProfile sleepProfile = new SleepProfile(this.profileInfo);
		
		// if the profile is actually enabled
		if (sleepProfile.isEnabled()) {
	
			Vector<String> computerCheckList = new Vector<String>();
			boolean aliasActive = false;
			
			// if alias is active, use this computer name
			if ((String)this.profileInfo.get("FQDN") != null) {
				computerCheckList.add((String)this.profileInfo.get("FQDN"));
				aliasActive = true;
			}
			// otherwise - use LDAP
			else {

				String ou = (String)this.profileInfo.get("OU");
				
				// determine if single computer or group of computers...
				PAWSServer.LOG_FILE.log(Level.ALL,ou);

				String domainName = "."+LDAPQuery.convertToFQDN(ou,false).toLowerCase();
				
				int ouLevel = LDAPQuery.ouCheck(ou,ConfigManager.getProperty("app.ldap.loginserver"+domainName),ConfigManager.getProperty("app.ldap.protocol"+domainName),ConfigManager.getProperty("app.ldap.port"+domainName),ConfigManager.getProperty("app.ldap.username"+domainName),ConfigManager.getProperty("app.ldap.password"+domainName),ConfigManager.getProperty("app.ldap.logindomain"+domainName),true,ConfigManager.getProperty("app.ldap.method"+domainName));
				
				PAWSServer.LOG_FILE.log(Level.ALL,"OU Level is:"+ouLevel);
				
				// a specific computer
				if (ouLevel == LDAPQuery.COMPUTER_LEVEL_IDENTIFIER) {
					PAWSServer.LOG_FILE.log(Level.ALL,"This level is a computer");
					computerCheckList.add(ou);
				}
				// chances are it is an OU with computers underneath
				else if (ouLevel == LDAPQuery.OU_LEVEL_IDENTIFIER) {
					
					PAWSServer.LOG_FILE.log(Level.ALL,"Identifier for LDAP params="+domainName);
					
					PAWSServer.LOG_FILE.log(Level.ALL,"This level is an OU with computers underneath");
					PAWSServer.LOG_FILE.log(Level.ALL,"Top level OU = "+sleepProfile.getTopLevelOU());
					PAWSServer.LOG_FILE.log(Level.ALL,"FQDN of DC = "+LDAPQuery.convertToFQDN(sleepProfile.getTopLevelOU(),false));
					computerCheckList = LDAPQuery.ouLookup(ConfigManager.getProperty("app.ldap.loginserver"+domainName),ConfigManager.getProperty("app.ldap.protocol"+domainName),ConfigManager.getProperty("app.ldap.port"+domainName),ConfigManager.getProperty("app.ldap.username"+domainName),ConfigManager.getProperty("app.ldap.password"+domainName),ConfigManager.getProperty("app.ldap.logindomain"+domainName),true,ConfigManager.getProperty("app.ldap.method"+domainName), sleepProfile.getTopLevelOU(),"computer",SearchControls.SUBTREE_SCOPE);
					
					PAWSServer.LOG_FILE.log(Level.ALL,"Computer Checklist = "+computerCheckList.toString());
				}
			}
		
			String sleepMode = sleepProfile.getSleepMode();
			
			StringTokenizer sleepModes = new StringTokenizer(sleepMode,",");
			
			PAWSServer.LOG_FILE.log(Level.INFO,"Processing profiles for:"+computerCheckList.toString());
			
			boolean inImmunity = TimeChecks.inImmunityPeriod();
			
			while (sleepModes.hasMoreElements()) {
				
				int thisMode = Integer.parseInt((String)sleepModes.nextElement());
			
				for (int i=0;i<computerCheckList.size();i++) {
					
					String computerName = computerCheckList.get(i);
					
					if (!aliasActive) {
						String fqdn = TextManip.convertToLDAPtoFQDN(computerName);	
				
						computerName = fqdn;
					}
					
					// server controlled sleep mode to sleep computers that are not logged in
					if (thisMode == SLEEPMODE_SERVER_CONTROLLED_LOGGEDOUT && !inImmunity) {	
						processWithServerControlledLoggedOut(sleepProfile,computerName);
					}
					// a forced sleep mode - doesn't care on the idle time of the user 
					else if (thisMode == SLEEPMODE_SERVER_CONTROLLED_SCHEDULED && !inImmunity) {
						processWithServerScheduledSleep(sleepProfile,computerName);
					}
					// a compassionate sleep mode - the server asks the client what its idle time is of the logged in user
					else if (thisMode == SLEEPMODE_SERVER_CONTROLLED_WARNING && !inImmunity) {
						processWithServerWarningSleep(sleepProfile,computerName);
					}
					// a compassionate sleep mode - the server asks the client what its idle time is of the logged in user
					else if (thisMode == SLEEPMODE_CLIENT_CONTROLLED_IDLE && !inImmunity) {
						processWithClientIdleSleep(sleepProfile,computerName);
					}
					else if (thisMode == WAKEMODE_SERVER_CONTROLLED_WAKE) {
						processWithServerWake(sleepProfile,computerName);
					}
				}
			}
		}
	}
	
	/**
	 * Called when a computer requires a wake to be administered.
	 * @param sleepProfile The sleep profile object containing all the required info from the database
	 * @param fqdn The fully qualified domain name of the computer to be woken
	 */
	private void processWithServerWake(SleepProfile sleepProfile, String fqdn) { 
		
		if (validateComputerFilter(sleepProfile.getComputerFilter(),fqdn)) {
			
			boolean checkResult = sleepProfile.isItTimeToSleep();
			
			if (checkResult) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Attempting to wake: "+fqdn);
				WakeOnLan.performWakeUpOnComputer(fqdn);
			}
		}
	}
	
	/**
	 * Yet to be implemented
	 * @param sleepProfile
	 * @param fqdn
	 */
	private void processWithServerWarningSleep(SleepProfile sleepProfile, String fqdn) {
		
	}
	
	/**
	 * Processes a sleep profile based off the client idle criteria
	 * @param sleepProfile The sleep profile object containing all the required info from the database
	 * @param fqdn The fully qualified domain name of the computer to be slept
	 */
	private void processWithClientIdleSleep(SleepProfile sleepProfile, String fqdn) {
		
		if (validateComputerFilter(sleepProfile.getComputerFilter(),fqdn)) {
			
			Hashtable<String,Long> idleTimes = SendMessage.requestIdleTimeMessage(fqdn, Integer.parseInt(ConfigManager.getProperty("app.net.secureservercommsport")));
			
			if (idleTimes != null) {
				// no one logged in, call logged out sleep method
				if (idleTimes.size() == 0) {
					PAWSServer.LOG_FILE.log(Level.ALL,fqdn+": No one logged in");
					processWithServerControlledLoggedOut(sleepProfile,fqdn);
				}
				else {
					
					boolean checkResult = sleepProfile.checkForIdleSleepRequirement(idleTimes);
				
					if (checkResult) {
						// start a new thread that will deal with the sleeping of this one computer
						SleepThread sleepIt = new SleepThread(fqdn);
						sleepIt.start();
					}
				}
			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Computer not responding on 5688 (cannot detect idle times): "+fqdn);
			}
		}
	}
	
	/**
	 * Processes a sleep profile based off the scheduled sleep criteria
	 * @param sleepProfile The sleep profile object containing all the required info from the database
	 * @param fqdn The fully qualified domain name of the computer to be slept
	 */
	private void processWithServerScheduledSleep(SleepProfile sleepProfile, String fqdn) {
	
		if (validateComputerFilter(sleepProfile.getComputerFilter(),fqdn)) {
			
			boolean checkResult = sleepProfile.isItTimeToSleep();
			
			if (checkResult) {
				// start a new thread that will deal with the sleeping of this one computer
				SleepThread sleepIt = new SleepThread(fqdn);
				sleepIt.start();
			}
		}
	}
	
	/**
	 * Processes a sleep profile based off the criteria by which no one is logged in to the computer
	 * @param sleepProfile The sleep profile object containing all the required info from the database
	 * @param fqdn The fully qualified domain name of the computer to be slept
	 */
	private void processWithServerControlledLoggedOut(SleepProfile sleepProfile, String fqdn) {
		
		if (validateComputerFilter(sleepProfile.getComputerFilter(),fqdn)) {
			
			// get the last logout time for the given computer
			long lastLogout = (new DBLogger()).getLastLogoutForComputer(fqdn.toUpperCase());
			
			PAWSServer.LOG_FILE.log(Level.ALL,"Last logout for "+fqdn+" was: "+lastLogout);
			
			// if there is no one logged in...
			if (lastLogout > -0l) {
				
				// pass in the last logout time and check if it meets the given sleep period specification
				boolean checkResult = sleepProfile.checkForSleepRequirement(lastLogout);
				
				// if true sleep it...
				if (checkResult) {
					
					// start a new thread that will deal with the sleeping of this one computer
					SleepThread sleepIt = new SleepThread(fqdn);
					sleepIt.start();
				}
			}
		}
	}
	
	/**
	 * Checks that the computer filter specified inthe sleep/wake profile matches the fqdn of the computer awaiting action.
	 * @param computerFilter The computer name filter e.g. a positive result would be computerFilter=isws, fqdn=isws0001
	 * @param fqdn The fully qualified domain name of the computer to be slept
	 * @return True if a match or filter is not in use, false if the fqdn does not start with the filter name
	 */
	private boolean validateComputerFilter(String computerFilter, String fqdn) {
		
		boolean passComputerFilter = false;
		
		if (computerFilter == null || computerFilter.length() == 0) {
			passComputerFilter = true;
		}
		else {
			// 	if the computer name starts with the computer criteria for the profile e.g. ISWS, ISGC....
			if (fqdn.startsWith(computerFilter) || computerFilter.length() == 0) {
				passComputerFilter = true;
			}
		}
		
		return passComputerFilter;
	}
}